/*
 * ============================================================================
 *
 *       Filename:  process.c
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  2014年08月26日 10时04分23秒
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  jianxi sun (jianxi), ycsunjane@gmail.com
 *   Organization:  
 *
 * ============================================================================
 */
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>

#include "thread.h"
#include "apstatus.h"
#include "process.h"
#include "cmdline.h"
#include "netlayer.h"
#include "randomarr.h"
#include "sockarr.h"
#include "system.h"
#include "arg.h"
#include "net.h"
#include "log.h"

struct sysstat_t sysstat = {
	.acuuid = {0},
	.isreg = 0,
	.sockarr = NULL,
	.sock = -1,
	.dmac = {0},
	.lock = PTHREAD_MUTEX_INITIALIZER,
};

#define SYSSTAT_LOCK() 	(LOCK(&sysstat.lock))
#define SYSSTAT_UNLOCK() (UNLOCK(&sysstat.lock))

static void ac_reconnect()
{
	assert(sysstat.sock < 0 && sysstat.sockarr == NULL);

	int fd;
	struct nettcp_t tcp;

	SYSSTAT_LOCK();
	while(1) {
		if(!sysstat.acaddr.sin_addr.s_addr)
			goto recheck;

		tcp.addr.sin_family = AF_INET;
		tcp.addr = sysstat.acaddr;
		if((fd = tcp_connect(&tcp)) < 0)
			goto recheck;

		sysstat.sock = fd;
		sysstat.isreg = 1;

		sysstat.sockarr = sockarr_insert(sysstat.sock, net_netrcv, NULL);
		sys_debug("connect ac: %s port: %d success: %d\n", 
			inet_ntoa(sysstat.acaddr.sin_addr),
			ntohs(sysstat.acaddr.sin_port),
			sysstat.sock);
		break;
recheck:
		sleep(1);
	}
	SYSSTAT_UNLOCK();
}

void ac_lost()
{
	sys_debug("ac lost\n");
	sockarr_delete(sysstat.sockarr, 0);
	sysstat.sock = -1;
	sysstat.sockarr = NULL;
}

static void *report_apstatus(void *arg)
{
	char *status;
	int statuslen, totallen;
	struct msg_ap_status_t *ap = NULL;

	int ret;
	while(1) {
		if(sysstat.sock < 0)
			ac_reconnect();

		status = apstatus();
		statuslen = strlen(status) + 1;
		totallen = sizeof(struct msg_ap_status_t) + statuslen;
		ap = safe_malloc(totallen);
		sys_debug("statuslen: %d, status: %s\n", statuslen, status);

		sysstat.lrandom = chap_get_random();
		fill_msg_header((void *)ap, MSG_AP_STATUS,
			sysstat.acuuid, sysstat.lrandom);
		strcpy(ap->status, status);

		chap_fill_msg_md5((void *)ap, 
			totallen, sysstat.rrandom);
		ret = sockarr_send(MSG_PROTO_TCP, sysstat.sock, 
			NULL, (void *)ap, totallen);
		if(ret <= 0) ac_lost();
		sys_debug("report ap status (next %d seconds later)\n", 
			args_info.repitv_arg);
		sleep(args_info.repitv_arg);
	}
	return NULL;
}

static void response_brd(struct msg_ac_brd_t *msg, int len, int proto)
{
	int totallen, statuslen;
	char *netstatus;
	struct msg_ap_brd_resp_t *resp;

	netstatus = apnetwork(args_info.cfgnic_arg);
	statuslen = strlen(netstatus) + 1;
	totallen = sizeof(struct msg_ap_brd_resp_t) + statuslen;
	resp = safe_malloc(totallen);
	sys_debug("statuslen: %d, netstatus: %s\n", statuslen, netstatus);

	/* generate random1 */
	fill_msg_header((void *)resp, MSG_AP_BRD_RESP, 
		msg->header.acuuid, 
		new_random(msg->header.smac));
	resp->isreg = htonl(sysstat.isreg);
	resp->acaddr = gconf.acaddr;
	strcpy(resp->apstatus, netstatus);

	/* calculate chap: 
	 * md5sum1 = packet + random0 + password */
	chap_fill_msg_md5((void *)resp, 
		totallen, msg->header.random);
	sockarr_send(proto, -1, &msg->header.smac[0], 
		(void *)resp, totallen);
	sys_debug("SEND: broadcast response packet\n");

	free(resp);
	free(netstatus);
}

/*
 * reponse_brd recv broadcast msg from ac and update sysstat
 * */
static void proc_brd(struct msg_ac_brd_t *msg, int len, int proto)
{
	if(proto != MSG_PROTO_ETH) {
		sys_err("broadcast packet can only received from dll\n");
		return;
	}

	if(len < sizeof(*msg)) {
		sys_err("receive error msg ac broadcast packet\n");
		return;
	}

	sys_debug("RECV: broadcast packet\n");
	response_brd(msg, len, proto);
}

static void proc_exec(char *cmd, int needrestart)
{
	if(!strlen(cmd)) return;
	sys_debug("exec: %s\n", cmd);

	system(cmd);
	if(needrestart) {
		net_restart();
	}
}

static void 
proc_req_reg(struct msg_ac_req_reg_t *msg, int len, int proto)
{
	CHECK_MSGLEN(msg, len);
	/* md5sum3 = packet + random1 + password */
	CHECK_CHAP(msg, len, get_random(msg->header.smac));

	sys_debug("Receive ac reg response packet\n");
	strncpy(sysstat.acuuid, msg->header.acuuid, UUID_LEN);
	memcpy(sysstat.dmac, msg->header.smac, ETH_ALEN);
	sysstat.acaddr = msg->acaddr;
	sysstat.rrandom =  msg->header.random;
	sys_debug("acaddr: %s, port: %d\n", 
		inet_ntoa(msg->acaddr.sin_addr), 
		ntohs(msg->acaddr.sin_port));
	proc_exec(msg->regcmd, msg->needrestart);
}

static void 
proc_accmd(struct msg_ac_cmd_t *msg, int len, int proto)
{
	CHECK_MSGLEN(msg, len);
	CHECK_UUID(msg->header.acuuid, sysstat.acuuid);
	CHECK_CHAP(msg, len, sysstat.lrandom);
	sys_debug("receive ac command packet\n");
	proc_exec(msg->cmd, msg->needrestart);
}

static int is_mine(struct msg_head_t *msg, int len)
{
	/* check packet len */
	if(len < sizeof(*msg)) {
		sys_err("receive ultrashort packet\n");
		return 0;
	}

	return 1;
}

void msg_proc(struct msg_head_t *msg, int len, int proto)
{
	sys_debug("RECV: receive a packet len: %d, proto: %d\n",
		len, proto);
	if(!is_mine(msg, len))
		return;

	switch(ntohl(msg->msg_type)) {
	case MSG_AC_BRD:
		proc_brd((void *)msg, len, proto);
		break;
	case MSG_AC_REQ_REG:
		proc_req_reg((void *)msg, len, proto);
		break;
	case MSG_AC_CMD:
		proc_accmd((void *)msg, len, proto);
		break;
	default:
		sys_warn("RECV: receive unknow packet: %x\n",
			msg->msg_type);
		break;
	}
}

void report_init()
{
	/* init remote ac address */
	sysstat.acaddr = gconf.acaddr;
	pthread_create_detach(report_apstatus, NULL);
}
